﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SequenceAssembler
{
	enum Base
	{
		A = 0, C = 1, G = 2, T = 3
	}

	class BaseOperation
	{
		public static byte baseToByte(char ch)
		{
			switch (ch)
			{
				case 'A':
					return (byte)Base.A;
				case 'C':
					return (byte)Base.C;
				case 'G':
					return (byte)Base.G;
				case 'T':
					return (byte)Base.T;
				default:
					Debug.Assert(false);
					return (byte)0xFF;
			}
		}

		public static char byteToBase(byte b)
		{
			switch (b)
			{
				case (byte)Base.A:
					return 'A';
				case (byte)Base.C:
					return 'C';
				case (byte)Base.G:
					return 'G';
				case (byte)Base.T:
					return 'T';
				default:
					Debug.Assert(false);
					return 'N';
			}
		}

		public static long kmerToLong(string kmer)
		{
			return kmerToLong(kmer.ToCharArray());
		}

		public static long kmerToLong(char[] kmer)
		{
			Debug.Assert(kmer.Length < 32);

			long res = 0;
			foreach (char ch in kmer)
			{
				res = (res << 2) | baseToByte(ch);
			}

			return res;
		}

        public static long kmerToLong(string kmer, int start, short count)
        {
            Debug.Assert(kmer.Length < 32);
            long res = 0;
            for (short i = 0; i < count; ++i)
            {
                res = (res <<2) | baseToByte(kmer[start]);
                start += 1;
            }
            return res;
        }

        public static string bitDequeToString(BitDeque bitDeque)
        {
			StringBuilder sb = new StringBuilder();
			for (uint i = 0; i < bitDeque.size(); ++i)
			{
				sb.Append(byteToBase(bitDeque[i]));
				if ((i + 1) % 60 == 0) {
					sb.Append('\n');
				}
			}
			return sb.ToString();
		}

		public static string reverseComplement(string read) {
			return new string(reverseComplement(read.ToCharArray()));
		}

		public static char[] reverseComplement(char[] read) {
			char[] fixread = new char[read.Length];
			for (int i = 0; i < fixread.Length; ++i)
			{
				int j = fixread.Length - 1 - i;
				switch (read[j])
				{
					case 'A':
						fixread[i] = 'T';
						break;
					case 'C':
						fixread[i] = 'G';
						break;
					case 'G':
						fixread[i] = 'C';
						break;
					case 'T':
						fixread[i] = 'A';
						break;
					default:
						Debug.Assert(false);
						fixread[i] = 'N';
						break;
				}
			}
			return fixread;
		}

		public static long neighborId(long id, Base b, short k) {
			long mask = (1L << (k + k)) - 1;
			return neighborId(id, b, mask);
		}

		public static long neighborId(long id, Base b, long mask)
		{
			long neighbor = (id << 2) | (byte)b;
			return neighbor & mask;
		}

		public static void unitTest()
		{
			string seq1 = "ACACACACACAC";
			string seq2 = "GTGTGTGTGTGT";

			long l1 = kmerToLong(seq1);
			long l2 = kmerToLong(seq2);

			Console.WriteLine(Convert.ToString(l1, 2));
			Console.WriteLine(Convert.ToString(l2, 2));

			BitDeque d1 = new BitDeque();
			BitDeque d2 = new BitDeque();
			d1.initialize(l1, (uint)seq1.Length);
			d2.initialize(l2, (uint)seq2.Length);

			StringBuilder sb1 = new StringBuilder();
			StringBuilder sb2 = new StringBuilder();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(byteToBase(d1[i]));
			}
			for (uint i = 0; i < d2.size(); ++i)
			{
				sb2.Append(byteToBase(d2[i]));
			}

			Console.WriteLine(sb1.ToString());
			Console.WriteLine(sb2.ToString());

			d1.push_front(0);
			d1.push_front(0);
			d1.push_front(0);
			d1.push_front(0);
			d1.push_back(1);
			d1.push_back(1);
			d1.push_back(1);
			d1.push_back(1);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_front(2);
			d2.push_back(3);
			d2.push_back(3);
			d2.push_back(3);
			d2.push_back(3);

			sb1.Clear();
			sb2.Clear();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(byteToBase(d1[i]));
			}
			for (uint i = 0; i < d2.size(); ++i)
			{
				sb2.Append(byteToBase(d2[i]));
			}
			Console.WriteLine(sb1);
			Console.WriteLine(sb2);

			d1.push_front_range(d2, 0, d2.size() / 2);
			d1.push_back_range(d2, d2.size() / 2, d2.size());
			sb1.Clear();
			for (uint i = 0; i < d1.size(); ++i)
			{
				sb1.Append(byteToBase(d1[i]));
			}

			Console.WriteLine(sb1);
		}
	}
}
